<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>[1.15]-数据库操作：基于NotORM的使用及优化 | PhalApi(π框架) - PHP轻量级开源接口框架 - 助你创造价值！</title>
    <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
    <meta content="yes" name="apple-mobile-web-app-capable">
    <meta content="black" name="apple-mobile-web-app-status-bar-style">
    <meta content="telephone=no" name="format-detection">
    <meta content="email=no" name="format-detection">
    <meta name="Author" content="PhalApi,ecitlm,dogstar">
    <meta name="description" content="[1.15]-数据库操作：基于NotORM的使用及优化，PhalApi是一个PHP轻量级开源接口框架，致力于快速开发接口服务。支持HTTP/SOAP/RPC等协议，可用于搭建接口/微服务/RESTful接口/Web Services。我们不断更新，保持生气；为接口负责，为开源负责！并承诺永久免费！">
    <meta name="keywords" content="PhalApi,phalapi,phalapi接口开发,后台接口开发,后台接口开发框架,接口开发,接口框架,开源接口框架,PHP后台接口开发,PHP接口开发,PHP接口框架,PHP后台接口框架,phalapi官网,PHP接口框架,php接口开发框架,php接口开发,php web框架,π框架,π开发框架,π接口框架,π接口开发框架,api接口,php接口,,PHP接口框架,phalapi文档,phalapi wiki,PhalApi文档,phalapi在线文档,phalapi官方文档">

    <link rel="stylesheet" type="text/css" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" media="screen">
    <link rel="stylesheet" type="text/css" href="https://cdn.bootcss.com/font-awesome/4.7.0/css/font-awesome.css" media="screen">
    <link rel="stylesheet" href="../css/main.css" />
    <link rel="stylesheet" href="../css/loading_bar.css" />
    <link rel="icon" href="http://webtools.qiniudn.com/dog_catch.png" type="image/x-icon" />
    <script src="../js/pace.min.js"></script>
	
	<!-- 代码高亮 -->
	<link rel="stylesheet" href="../css/highlight.min.css">
	<script src="../js/highlight.min.js"></script>
	<script>hljs.initHighlightingOnLoad();</script>
    
</head>

<body>
    <!-- navbar start -->
    <div class="navbar navbar-default navbar-fixed-top" id="mainnav" role="navigation">
        <div class="container">
            <div class="navbar-header">
                <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target=".navbar-collapse">
                    <span class="sr-only">Toggle navigation</span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
                <a class="navbar-brand website_name" href="/"><!-- <span>PhalApi</span> --><strong></strong></a>
            </div>
            <div class="navbar-collapse collapse" aria-expanded="false" style="height: 1px;">
                <ul class="nav navbar-nav navbar-right" id="nav_bar">
                    <li><a href="https://www.phalapi.net/"><span class="glyphicon glyphicon-home"></span></a></li>
                    <li><a href="https://www.phalapi.net/download.html">下载</a></li>
                    <li class="active"><a href="https://www.phalapi.net/wikis/">文档</a></li>
                    <li><a href="https://www.phalapi.net/donate.html">贡献</a></li>
                    <li><a href="#">|</a></li>
                    <li><a href="http://qa.phalapi.net/" target="_blank">社区</a></li>
                    <li><a href="http://www.ituring.com.cn/book/2405" target="_blank">书籍</a></li>
                    <li><a href="http://demo.phalapi.net/listAllApis.php" target="_blank">体验</a></li>
                    <li><a href="https://github.com/phalapi/phalapi-library" target="_blank">扩展</a></li>
                </ul>
            </div>
            <!--/.nav-collapse -->
        </div>
    </div>
    <!-- navbar end -->


    <div id="content">
        <div class="container">
            <div class="row row-md-flex row-md-flex-wrap">
                <h4><a href="/wikis/%5B1.14%5D-%E7%BB%9F%E4%B8%80%E7%9A%84%E8%BF%94%E5%9B%9E%E6%A0%BC%E5%BC%8F%E5%92%8C%E7%BB%93%E6%9E%84%EF%BC%9Aret-data-msg.html">上一章</a>   <a href="/wikis/">文档首页</a>   <a href="/wikis/%5B1.16%5D-%E9%85%8D%E7%BD%AE%E8%AF%BB%E5%8F%96%EF%BC%9A%E5%86%85%E5%A4%96%E7%BD%91%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE%E7%9A%84%E5%AE%8C%E7%BE%8E%E5%88%87%E6%8D%A2.html">下一章</a></h4>
<hr />
<p><em>有些裁缝只会塞塞衬里，修修补补，而有些裁缝却能做出新东西--他们之间隔着一条深渊。 -- 《外套》</em>  </p>
<h2>1.15.1 NotORM官网</h2>
<p>这里使用了NotORM进行DB操作，具体的数据库操作使用文档请见NotORM官网： <a href="http://www.notorm.com"><a href="http://www.notorm.com">http://www.notorm.com</a></a>  </p>
<h3>(1) 基本CURD</h3>
<pre><code class="language-php">//查询
$row = DI()-&gt;notorm-&gt;user-&gt;where('id', 1)-&gt;fetch();

//更新
$data = array('name' =&gt; 'test', 'update_time' =&gt; time());
DI()-&gt;notorm-&gt;user-&gt;where('id', 1)-&gt;update($data);

//插入（须是同一个对象才能正确获取插入的ID）
$data = array('name' =&gt; 'phalapi');
$userORM = DI()-&gt;notorm-&gt;user;
$userORM-&gt;insert($data);
$id = $userORM-&gt;insert_id();

//删除
DI()-&gt;notorm-&gt;user-&gt;where('id', 1)-&gt;delete();</code></pre>
<h3>(2)update相同的数据的判断</h3>
<p>在使用update操作时，如果更新的数据和原来的一样，则会返回0（影响0行）。这时，会和更新失败（同样影响0行）混淆。  </p>
<p>但NotORM是一个优秀的类库，所以提供了优秀的解决文案。我们在使用update时，只须了解这两者返回的结果的微妙区别即可。<br />
因为失败异常时，返回false；而相同数据更新会返回0。即：  </p>
<ul>
<li>1、update相同的数据时，返回0，严格来说是：int(0)</li>
<li>2、update失败时，如更新一个不存在的字段，返回false，即：bool(false)</li>
</ul>
<p>用代码表示，就是：</p>
<pre><code class="language-php">$rs = DI()-&gt;notorm-&gt;user-&gt;where('id', $userId)-&gt;update($data);

if ($rs &gt;= 1) {
    //成功
} else if ($rs === 0) {
    //相同数据，无更新
} else if ($rs === false) {
    //更新失败
}</code></pre>
<p>以下单元测试代码，可以验证上面的判断：</p>
<pre><code class="language-php">    public function testUpdateOk()
    {
        $userId = 87;

        $rs = DI()-&gt;notorm-&gt;user-&gt;where('id', $userId)-&gt;update(array('reg_time' =&gt; time()));

        $this-&gt;assertSame(1, $rs);
    }

    public function testUpdateZero()
    {
        $userId = 1;

        $rs = DI()-&gt;notorm-&gt;user-&gt;where('id', $userId)-&gt;update(array('username' =&gt; 'aevit'));

        $this-&gt;assertSame(0, $rs);
    }

    public function testUpdateFail()
    {
        $userId = 1;

        $rs = DI()-&gt;notorm-&gt;user-&gt;where('id', $userId)-&gt;update(array('wrong_username' =&gt; 'aevit'));

        $this-&gt;assertSame(FALSE, $rs);
    }</code></pre>
<h3>(3)简单的关联查询</h3>
<p>如果是简单的关联查询，可以使用NotORM支持的写法，这样的好处在于我们使用了一致的开发，并且能让PhalApi框架保持分布式的操作方式（注意，关联的表仍然需要在同一个数据库）。  </p>
<p>以下是一个简单的示例。  </p>
<p>假设我们有这样的数据：</p>
<pre><code class="language-php">INSERT INTO `phalapi_user` VALUES ('1', 'wx_edebc877070133c65161d00799e00544', 'weixinName', '******', '4CHqOhe1Jxi3X9HmRfPOXygDnU267eCA', '1431790647', 'phpunit.png');
INSERT INTO `phalapi_user_session_0` VALUES ('1', '1', 'ABC', '', '0', '0', '0', null);</code></pre>
<p>那么对应关联查询的代码如下面：</p>
<pre><code class="language-php">    public function testLeftJoin()
    {
        $rs = DI()-&gt;notorm-&gt;user_session_0
            -&gt;select('expires_time, user.username, user.nickname')
            -&gt;where('token', 'ABC')
            -&gt;fetchRow();

        var_dump($rs);
    }
</code></pre>
<p>运行一下，我们可以看到这样的输出：</p>
<pre><code class="language-php">SELECT expires_time, user.username, user.nickname FROM phalapi_user_session_0 LEFT JOIN phalapi_user AS user ON phalapi_user_session_0.user_id = user.id WHERE (token = 'ABC') LIMIT 1;

.[1 - 0.06318s]SELECT expires_time, user.username, user.nickname FROM phalapi_user_session_0 LEFT JOIN phalapi_user AS user ON phalapi_user_session_0.user_id = user.id WHERE (token = 'ABC') LIMIT 1;&lt;br&gt;
array(3) {
  ["expires_time"]=&gt;
  string(1) "0"
  ["username"]=&gt;
  string(35) "wx_edebc877070133c65161d00799e00544"
  ["nickname"]=&gt;
  string(10) "weixinName"
}</code></pre>
<p>这样，我们就可以实现关联查询的操作。按照NotORM官网的说法，则是：</p>
<blockquote>
<p>If the dot notation is used for a column anywhere in the query (&quot;$table.$column&quot;) then NotORM automatically creates left join to the referenced table. Even references across several tables are possible (&quot;$table1.$table2.$column&quot;). Referencing tables can be accessed by colon: $applications-&gt;select(&quot;COUNT(application_tag:tag_id)&quot;).</p>
</blockquote>
<p>-&gt;select('expires_time, user.username, user.nickname')这一行调用将会【自动产生关联操作】，而ON 的字段，则是这个字段关联你配置的【表结构】，外键默认为： 表名_id 。</p>
<h3>(4)加1操作</h3>
<p>NotORM已提供了NotORM_Literal，其用法如下：  </p>
<pre><code class="language-php">DI()-&gt;notorm-&gt;user-&gt;where('id', 1)-&gt;update(array('age' =&gt; new NotORM_Literal("age + 1")));</code></pre>
<p>当需要更新为当前时间，可以：  </p>
<pre><code class="language-php">$array = array(
    "title" =&gt; "NotORM",
    "author_id" =&gt; null,
    "created" =&gt; new NotORM_Literal("NOW()"),
);</code></pre>
<h2>1.15.2 NotORM的优化</h2>
<p>但为了更符合项目的开发，这里对NotORM的底层作了升级修改，以下为主要修改点和新的使用：  </p>
<h3>(1)将原来返回的结果全部从对象改成数组</h3>
<p>对原来的大部分使用无特别影响，可按原来的方式开发。主要目的是为了更方面处理返回的数据，以及简化对结果的再解析，简单明了。<br />
如：</p>
<pre><code class="language-php">DI()-&gt;notorm-&gt;user-&gt;where('username = ?', 'dogstar')-&gt;fetch();</code></pre>
<p>返回的将是一个数组：</p>
<pre><code class="language-php">array(7) {
  ["id"]=&gt;
  string(3) "180"
  ["username"]=&gt;
  string(17) "dogstar"
  ["regtime"]=&gt;
  string(10) "1414811954"
  //...
}</code></pre>
<h3>(2)提供获取全部结果的接口 - fetchAll() / fetchRows()</h3>
<p>如：  </p>
<pre><code class="language-php">$rows = DI()-&gt;notorm-&gt;event_picurl-&gt;where('eid', $eids)-&gt;fetchAll();</code></pre>
<p>或：</p>
<pre><code class="language-php">$rows = DI()-&gt;notorm-&gt;event_picurl-&gt;where('eid', $eids)-&gt;fetchRows();</code></pre>
<p>即可获取全部的数据，不再受限于分页。<br />
这里提供了fetchAll()和fetchRows()两种等效的操作，是为了减少记忆的痛苦，下同。  </p>
<h3>(3)提供更灵活的查询方式 - queryAll() / queryRows()</h3>
<p>当需要进行复杂的SQL查询时，可以使用此接口，如：<br />
(注意:limit替换值:start和:num必须使用int类型)</p>
<pre><code class="language-php">$sql = 'select * from example AS ep LEFT JOIN user AS u ON ep.ui
d = u.id  where ep.touid = :userId ORDER BY dateline desc LIMIT :start,:num';
$params = array(':userId' =&gt; $userId, ':start' =&gt; $start, ':num' =&gt; $num);
$rs= DI()-&gt;notorm-&gt;example-&gt;queryAll($sql, $params);</code></pre>
<p>或：</p>
<pre><code class="language-php">$rs= DI()-&gt;notorm-&gt;example-&gt;queryRows($sql, $params);</code></pre>
<h3>(4)limit 操作的调整</h3>
<p>取消了NotORM中对OFFSET关键字的使用，改用逗号的写法，修改后正确的使用方法应该是：  </p>
<pre><code class="language-php">$table-&gt;limit(10);  // limit 10   # 查询前10个

$table-&gt;limit(5, 10); // limit 5,10   # 从第5个位置开始，查询前10个</code></pre>
<h3>(5)禁止全表删除，防止误删</h3>
<p>出于对数据的保护，当执行删除操作却又没有任何where条件时，将会禁止进行全表操作。如：  </p>
<pre><code class="language-php">    public function testDeleteAll()
    {
        DI()-&gt;notorm-&gt;user-&gt;delete();
    }</code></pre>
<p>可以看到：  </p>
<pre><code class="language-php">$ phpunit --filter testDeleteAll ./Api/Api_User_Test.php 
PHPUnit 4.3.4 by Sebastian Bergmann.

E

Time: 315 ms, Memory: 6.25Mb

There was 1 error:

1) PhpUnderControl_ApiUser_Test::testDeleteAll
Exception: sorry, you can not delete the whole table --dogstar
</code></pre>
<h3>(6)添加&amp; __sql__ =1请求参数，可开启HTTP调试模式</h3>
<p>当处于debug模式时，可以输入执行的全部SQL语句，以便调试。   </p>
<p>如：  </p>
<pre><code class="language-php">SELECT times FROM tpl_user_session_10 WHERE (user_id = ?); -- '74110'
{"ret":0,"data":{"code":0},"msg":""}</code></pre>
<h3>(7)关于NotORM中fetch()操作没有limit 1的处理方案 - fetchOne() / fetchRow()</h3>
<p>之前，有开发同学提及到，为什么notorm的基类fetch为啥没用limit(1)呢。后来，我去发了下NotORM的写法，确实做得很微妙。<br />
其实NotORM之所以没有在fetch()里面自动limit 1是因为，你可以循环地获取数据，如：</p>
<pre><code class="language-php">$user = DI()-&gt;notorm-&gt;user-&gt;select('id, username, nickname')-&gt;where('id &gt; ?', 0)-&gt;limit(3);
while(($row = $user-&gt;fetch())) {
    var_dump($row);
 }</code></pre>
<p>但是，更多情况下，我们只需要获取某一行的数据，上面的做法会造成不必要的SQL查询。为了保留原来的写法，我特意添加扩展了一个新的操作：fetchRow()，用法同fetch()，但只会取第一条。<br />
以下是使用示例：  </p>
<pre><code class="language-php">$rs = DI()-&gt;notorm-&gt;user-&gt;select('id, username, nickname')-&gt;where('id &gt; ?', 0)-&gt;fetchRow());

var_dump($rs);

//结果输出类如：
array(3) {
  ["id"]=&gt;
  string(1) "1"
  ["username"]=&gt;
  string(5) "aevit"
  ["nickname"]=&gt;
  string(4) "test"
}

//对应执行的SQL语句：
[2 - 0.06544s]SELECT id, username, nickname FROM fami_user WHERE (id &gt; ?) LIMIT 1; -- 0&lt;br&gt;</code></pre>
<p>如果，我们只需要获取这一行的某个字段，也可以像fetch()那样使用，即：</p>
<pre><code class="language-php">$rs = DI()-&gt;notorm-&gt;user-&gt;select('id, username, nickname')-&gt;where('id &gt; ?', 0)-&gt;fetchRow('nickname'));

var_dump($rs);

//结果输出类如：
string(4) "test"

//纪录不存在时，返回 bool(false)</code></pre>
<h3>(8)显式的SQL语法异常提示</h3>
<p>很多时候，在开发时，我们对数据库操作一开始会存在一些SQL语法的问题，PDO会返回false,且原来NotORM也是使用 <strong>静默方式</strong> 来处理这类错误，从而使得开发人员有时难以发现这些问题，除非将调试的SQL手动放到数据库执行才能发现问题所在。  </p>
<p>为了能给开发同学更早、更直观的方式查看问题的所在，这里对NotORM底层进行了调整，使用了 <strong>显式方式</strong> 的策略来处理，即：直接抛出PDO异常。  </p>
<p>如：</p>
<pre><code class="language-php">$userId = 1;

//OK
$rs = DI()-&gt;notorm-&gt;user-&gt;select('username')-&gt;where('id', $userId)-&gt;fetchOne();

//WRONG
$rs = DI()-&gt;notorm-&gt;user-&gt;select('wrong_username')-&gt;where('id', $userId)-&gt;fetchOne();
</code></pre>
<p>将会看到：</p>
<pre><code class="language-php">[1 - 0.06437s]SELECT username FROM fami_user WHERE (id = 1) LIMIT 1;&lt;br&gt;
[2 - 0.06496s]SELECT wrong_username FROM fami_user WHERE (id = 1) LIMIT 1;&lt;br&gt;

PDOException: Unknown column 'wrong_username' in 'field list'</code></pre>
<h3>(9)复杂的关联查询</h3>
<p>如果是复杂的关联查询，则是建议使用原生态的SQL语句，但我们仍然可以保持很好的写法，如这样一个示例：</p>
<pre><code class="language-php">        $sql = 'SELECT t.id, t.team_name, v.vote_num '
            . 'FROM phalapi_team AS t LEFT JOIN phalapi_vote AS v '
            . 'ON t.id = v.team_id '
            . 'ORDER BY v.vote_num DESC';
        $rows = $this-&gt;getORM()-&gt;queryAll($sql, array());
</code></pre>
<p>注意，此时的表需要使用全名，即自带前缀。这样也可以实现更自由的关联查询。   </p>
<h3>(10)事务操作</h3>
<p>关于事务操作，可以参考 <a href="http://www.notorm.com/#api">NotORM官网</a> 的说明：  </p>
<pre><code>$db-&gt;transaction = $command Assign 'BEGIN', 'COMMIT' or 'ROLLBACK' to start or stop transaction </code></pre>
<p>即：  </p>
<pre><code>//第一步：先指定待进行事务的数据库（通过获取一个notorm表实例来指定；否则会提示：PDO There is no active transaction）
$user = DI()-&gt;notorm-&gt;user;

//第二步：开启事务开关（此开关会将当前全部打开的数据库都进行此设置）
DI()-&gt;notorm-&gt;transaction = 'BEGIN';

//第三步：进行数据库操作
$user-&gt;insert(array('name' =&gt; 'test1',));
$user-&gt;insert(array('name' =&gt; 'test2',));

//第四：提交/回滚
DI()-&gt;notorm-&gt;transaction = 'COMMIT';
//DI()-&gt;notorm-&gt;transaction = 'ROLLBACK';</code></pre>
<h4>推荐使用PhalApi的事务操作方式</h4>
<p>PhalApi一开始对事务这块考虑不周，后来发现很多同学、很多项目都需要用到数据库事务操作。<br />
基于此，在不破坏原来的代码基础上，我们决定在PhalApi_DB_NotORM上添加对数据库维度的事务操作支持。  </p>
<p>示例简单如下：  </p>
<pre><code>    public function testTransactionCommit()
    {
        //Step 1: 开启事务
        DI()-&gt;notorm-&gt;beginTransaction('db_demo');

        //Step 2: 数据库操作
        DI()-&gt;notorm-&gt;user&gt;insert(array('name' =&gt; 'test1'));
        DI()-&gt;notorm-&gt;user&gt;insert(array('name' =&gt; 'test2'));

        //Step 3: 提交事务
        DI()-&gt;notorm-&gt;commit('db_demo');
        // DI()-&gt;notorm-&gt;rollback('db_demo'); // 回滚

    }</code></pre>
<blockquote>
<p>温馨提示： 以上操作，须PhalApi 1.3.1 及以上版本才能支持。  </p>
</blockquote>
<h3>(11)扩展对非MySQL数据库的支持</h3>
<p>PhalApi使用的是NotORM来进行数据库操作，而NotORM底层则是采用了PDO。目前，NotORM支持： MySQL, SQLite, PostgreSQL, MS SQL, Oracle (Dibi support is obsolete)。  </p>
<p>但需要注意的是，PhalApi本身对NotORM进行了修改，需要调整一下代码才能更好地支持除MySQL外的数据库。
即使NotORM不支持的数据库，你也可以轻松通过添加扩展的方式来支持。如：   </p>
<p>首先，定制自己的数据库连接的PDO。  </p>
<pre><code>class Common_MyDB extends PhalApi_DB_NotORM {

    protected function createPDOBy($dbCfg) {
        /* Connect to an ODBC database using driver invocation */
    $dsn = 'uri:file:///usr/local/dbconnect';
    return new PDO($dsn, $dbCfg['user'], $dbCfg['password']);
    }
}</code></pre>
<p>随后，在初始化文件init.php中重新注册DI()-&gt;notorm即可，如：</p>
<pre><code>//数据操作 - 基于NotORM，$_GET['__sql__']可自行改名
DI()-&gt;notorm = function() {
    $debug = !empty($_GET['__sql__']) ? true : false;
    return new Common_MyDB(DI()-&gt;config-&gt;get('dbs'), $debug);
};</code></pre>
<h2>1.15.3 可选的Model基类</h2>
<h3>(1)表数据入口模式</h3>
<p>我们一直在考虑，是否应该提供数据库的基本操作支持，以减少开发人员重复手工编写基本的数据操作。  </p>
<p>最后，我们认为是需要的。然后就引发了新的问题：是以继承还是以委托来支持？  </p>
<p>委托有助于降低继承的层级，但仍然需要编写同类的操作然后再次委托。所以，这里提供了基于NotORM的Model基类：PhalApi_Model_NotORM。  </p>
<p>然而提供这个基类还是会遇到一些问题，例如：如何界定基本操作？如何处理分表存储？如何支持定制化？  </p>
<p>由于我们这里的Model使用了 <strong>“表数据入口”</strong> 模式，而不是“行数据入口”，也不是“活动纪录”，也不是复杂的“数据映射器”。所以在使用时可以考虑是否需要此基类。即使这样，你也可以很轻松转换到“行数据入口”和“活动纪录”模式。这里，PhalApi中的Model是更广义上的数据源层（后面会有更多说明），因此对应地PhalApi_Model_NotORM基类充当了数据库表访问入口的对象，处理表中所有的行。  </p>
<h3>(2)规约层的CURD</h3>
<p>在明白了Model基类的背景后，再来了解其具体的操作和如何继承会更有意义。  </p>
<p>而具体的操作则与数据表的结构相关，在“约定编程”下：即每一个表都有一个主键（通常为id，也可以自由配置）以及一个序列化LOB字段ext_data。我们很容易想到Model接口的定义（注释已移除，感兴趣的同学可查看源码）：</p>
<pre><code class="language-php">interface PhalApi_Model {

    public function get($id, $fields = '*');

    public function insert($data, $id = NULL);

    public function update($id, $data);

    public function delete($id);
}
</code></pre>
<p>上面的接口在规约层上提供了基于表主键的CURD基本操作，在具体实现时，需要注意两点：一是分表的处理；另一点则是LOB字段的序列化。  </p>
<h3>(3)不使用Model基类的写法</h3>
<p>由于我们使用了NotORM进行数据库的操作，所以这里也提供了基于NotORM的基类：PhalApi_Model_NotORM。下面以我们熟悉的获取用户的基本信息为例，说明此基类的使用。</p>
<p>为唤醒记忆，下面贴上Model_User类原来的代码：</p>
<pre><code class="language-php">// $ vim ./Demo/Model/User.php

&lt;?php

class Model_User {

    public function getByUserId($userId) {
        return DI()-&gt;notorm-&gt;user-&gt;select('*')-&gt;where('id = ?', $userId)-&gt;fetch();
    }
}</code></pre>
<p>对应的调用：</p>
<pre><code class="language-php">$model = new Model_User();
$rs = $model-&gt;getByUserId($userId);</code></pre>
<h3>(4)继承Model基类的写法</h3>
<p>若继承于PhalApi_Model_NotORM，则是：</p>
<pre><code class="language-php">// $ vim ./Demo/Model/User.php

&lt;?php

class Model_User extends PhalApi_Model_NotORM {

    protected function getTableName($id) {
        return 'user';
    }
}</code></pre>
<p>从上面的代码可以看出，基类已经提供了基于主键的CURD操作，但我们需要钩子函数以返回对应的表名。相应地，外部调用则调整为：</p>
<pre><code class="language-php">$model = new Model_User();
$rs = $model-&gt;get($userId);</code></pre>
<p>再进一步，我们可以得到其他的基本操作：</p>
<pre><code class="language-php">$model = new Model_User();

//查询
$row = $model-&gt;get(1);
$row = $model-&gt;get(1, 'id, name'); //取指定的字段
$row = $model-&gt;get(1, array('id', 'name')); //可以数组取指定要获取的字段

//更新
$data = array('name' =&gt; 'test', 'update_time' =&gt; time());
$model-&gt;update(1, $data); //基于主键的快速更新

//插入
$data = array('name' =&gt; 'phalapi');
$id = $model-&gt;insert($data);
//$id = $model-&gt;insert($data, 5); //如果是分表，可以这样指定

//删除
$model-&gt;delete(1);</code></pre>
<h2>1.15.4 定制化你的Model基类</h2>
<p>正如上面提及到的两个问题：LOB序列化和分表处理。所以，如果PhalApi现有就此两问题的解决方案不能满足项目的需求，可作定制化处理。  </p>
<h3>(1)LOB序列化</h3>
<p>先是LOB序列化，考虑到有分表的存在，当发生数据库变更时（特别在线上环境）会有一定的难度和风险，因此引入了扩展字段ext_data。当然，此字段也应对数据库变更的同时，也可以作为简单明了的值对象的大对象。序列化LOB首先要考虑的问题是使用二进制（BLOB）还是文本（CLOB），出于通用性、易读性和测试性，我们目前使用了json格式的文本序列化。所以，如果考虑到空间或性能问题（在少量数据下我认为问题不大，如果数据量大，应该及时重新调整数据库表结构），可以重写formatExtData() &amp; parseExtData()。  </p>
<p>如改成serialize序列化：</p>
<pre><code class="language-php">abstract class Common_Model_NotORM extends PhalApi_Model_NotORM {

    /**
     * 对LOB的ext_data字段进行格式化(序列化)
     */
    protected function formatExtData(&amp;$data) {
        if (isset($data['ext_data'])) {
            $data['ext_data'] = serialize($data['ext_data']);
        }
    }

    /**
     * 对LOB的ext_data字段进行解析(反序列化)
     */
    protected function parseExtData(&amp;$data) {
        if (isset($data['ext_data'])) {
            $data['ext_data'] = unserialize($data['ext_data'], true);
        }
    }

    // ...
}</code></pre>
<p>将Model类继承于Common_Model_NotORM后，  </p>
<pre><code class="language-php">// $ vim ./Demo/Model/User.php

&lt;?php
class Model_User extends Common_Model_NotORM {
   //...
}</code></pre>
<p>就可以轻松切换到序列化，如：  </p>
<pre><code class="language-php">$model = new Model_User();

//带有ext_data的更新
$extData = array('level' =&gt; 3, 'coins' =&gt; 256);
$data = array('name' =&gt; 'test', 'update_time' =&gt; time(), 'ext_data' =&gt; $extData);
$model-&gt;update(1, $data); //基于主键的快速更新
</code></pre>
<h3>(2)分表处理</h3>
<p>其次是分表处理，同样考虑到分表的情况，以及不同的表可能配置不同的主键表，而基于主键的CURD又必须要先知道表的主键名才能进行SQL查询。所以，问题就演变成了如何找到表的主键名。这里可以自动匹配，也可以手工指定。自动匹配是智能的，因为当我们更改表的主键时，可以自动同步更新而不需要担心遗漏（虽然这种情况很少发生）。手工指定可以大大减少系统不必要的匹配操作，因为我们开发人员也知道数据库的主键名是什么，但需要手工编写一些代码。在这里，提供了可选的手工指定，即可重写getTableKey($table)来指定你的主键名。</p>
<p>如，当我们的表的主键都固定为id时：</p>
<pre><code class="language-php">abstract class Common_Model_NotORM extends PhalApi_Model_NotORM {

    protected function getTableKey($table) {
        return 'id';
    }
}</code></pre>
<hr />
<h4><a href="/wikis/%5B1.14%5D-%E7%BB%9F%E4%B8%80%E7%9A%84%E8%BF%94%E5%9B%9E%E6%A0%BC%E5%BC%8F%E5%92%8C%E7%BB%93%E6%9E%84%EF%BC%9Aret-data-msg.html">上一章</a>   <a href="/wikis/">文档首页</a>   <a href="/wikis/%5B1.16%5D-%E9%85%8D%E7%BD%AE%E8%AF%BB%E5%8F%96%EF%BC%9A%E5%86%85%E5%A4%96%E7%BD%91%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE%E7%9A%84%E5%AE%8C%E7%BE%8E%E5%88%87%E6%8D%A2.html">下一章</a></h4><div style="float: left">
<h4>
<a href="http://qa.phalapi.net/">还有疑问？欢迎到社区提问！</a>
&nbsp;&nbsp;
<a href="http://docs.phalapi.net/#/v2.0/">切换到PhalApi 2.x 开发文档。</a>
</h4>
</div>
            </div>
        </div>
    </div>

 <!-- 广告位 -->
<div class="grid-wrapper desktop-only">
	<p align="center">
		<a href="http://7xiz2f.com1.z0.glb.clouddn.com/%E6%88%91%E7%9A%84%E5%90%8D%E5%AD%97%E5%8F%AB%EF%BC%9A%CF%80%E6%A1%86%E6%9E%B6%20-%20PhalApi%202016%E5%B9%B4%E5%BA%A6%E5%BC%80%E6%BA%90%E6%80%BB%E7%BB%93%20-%20%E5%AE%98%E6%96%B9%E5%87%BA%E5%93%81.pdf" target="blank"><img width="950" height="100" src="http://7xiz2f.com1.z0.glb.clouddn.com/ad_20170104.png"></a>
		<a href="http://www.itran.cc/" target="blank"><img width="950" height="100" src="http://7xslqv.com1.z0.glb.clouddn.com/images/9/69/b88bc92455dc1239f9a5bf8d72929.png"></a>
		<a href="http://web-tools.phalapi.net/" target="blank"><img width="950" height="100" src="http://7xslqv.com1.z0.glb.clouddn.com/images/7/d0/88f523566c482296aecc43d185ca2.png"></a>
	</p>
</div>

        <!-- footer -->
        <div class="footer">
            <div class="link container ">
                <div class=" col-xs-7 col-sm-7 col-md-7 col-lg-7 ">
                    <h3>PhalApi (π框架)</h3>
                    <p>一个轻量级PHP开源接口框架，专注于接口服务开发，支持HTTP/SOAP/RPC协议，拥有自动生成的在线文档、多种开发语言的客户端SDK包以及可重用的扩展类库，可用于快速搭建微服务、RESTful接口或Web
                        Services。</p>
                    <!-- <p>:</p> -->
                    <a href="https://www.phalapi.net/" target="_blank"> PhalApi </a>
                    <span>|</span>
                    <a href="http://qa.phalapi.net/" target="_blank">社区</a>
                    <span>|</span>
                    <a href="download.html" target="_blank">下载</a>
                    <span>|</span>
                    <a href="/wikis/">文档</a>
                    <span>|</span>
                    <a href="http://www.ituring.com.cn/book/2405" target="_blank">书籍</a>
                    <span>|</span>
                    <a href="http://demo.phalapi.net/listAllApis.php" target="_blank">体验</a>
                    <span>|</span>
                    <a href="donate.html" target="_blank">贡献</a>
                    <span>|</span>
                    <a href="https://github.com/phalapi/phalapi-library" target="_blank">扩展</a>
                    <span>|</span>
                    <a href="/license.html" target="_blank">许可</a>
                    <span>|</span>
                    <a href="/about.html" target="_blank">关于</a>
                    <span>|</span>
                    <a href="https://www.phalapi.net/wikis/en/" target="_blank">English</a>

                </div>
                <div class=" col-xs-5 col-sm-5 col-md-5 col-lg-5 contact_us ">
                    <h3>联系我们</h3>
                    <a href="https://github.com/phalapi/phalapi"><img src="../images/github.png" alt="github" /></a>
                    <a href="http://weibo.com/p/100808d236e99beb645bfb56ed1c37dde9b8bd?k=phalapi%E5%BC%80%E6%BA%90%E6%A1%86%E6%9E%B6&from=501&_from_=huati_topic"><img src="../images/weibo.png"  alt="weibo" /></a>
                    <a href="http://7xslqv.com1.z0.glb.clouddn.com/images/1/b0/894fde762850cb11250253f7d5ede.png"><img src="../images/zfb.png"  alt="zfb" /></a>
					<a target="_blank" href="//shang.qq.com/wpa/qunwpa?idkey=f16292029e59975d4ccfccc70ed87aa5c5646df512e83b0e9e1270e8368be461"><img border="0" src="images/qq.png" alt="π框架 - PhalApi交流群②" title="π框架 - PhalApi交流群②"></a>
					<a href="http://git.oschina.net/dogstar/PhalApi"><img src="../images/git-oschina.png"  alt="git-oschina" /></a>
                </div>
                <div class="cls"></div>
                <div class="text-center copy_right"> ©2015-2017 PhalApi All Rights Reserved. <a href="http://www.miitbeian.gov.cn" target="_blank">粤ICP备15028808号</a>

				<script type="text/javascript">var cnzz_protocol = (("https:" == document.location.protocol) ? " https://" : " http://");document.write(unescape("%3Cspan id='cnzz_stat_icon_1255326144'%3E%3C/span%3E%3Cscript src='" + cnzz_protocol + "s4.cnzz.com/z_stat.php%3Fid%3D1255326144%26show%3Dpic' type='text/javascript'%3E%3C/script%3E"));</script>
                </div>
            </div>
        </div>
    </div>


    <a href="https://github.com/phalapi"><img id="gitHub_fllow" style="position: fixed; top: 0; right: 0; border: 0;z-index:9999" src="https://camo.githubusercontent.com/652c5b9acfaddf3a9c326fa6bde407b87f7be0f4/68747470733a2f2f73332e616d617a6f6e6177732e636f6d2f6769746875622f726962626f6e732f666f726b6d655f72696768745f6f72616e67655f6666373630302e706e67" alt="Fork me on GitHub" data-canonical-src="https://s3.amazonaws.com/github/ribbons/forkme_right_orange_ff7600.png"></a>


    <script src="../js/jquery-1.11.1.min.js"></script>
    <script src="../js/bootstrap.min.js"></script>
</body>

</html>